#pragma once
#include <SOIL.h>
#include "geometry.hpp"

class heightmap {
private:
	const int POS_COMPONENT_NUM = 6; // position + normal

private:
	int _width, _height;
	float *_vertices;
	int _numelement;
	short *_elements;
	GLuint _vbo, _ebo;

private:
	void
	calc_numelement() {
		_numelement = (_width - 1) * (_height - 1) * 2 * 3; // quad num * 2 triangles * three vertices
	}

	point
	getpoint(unsigned char *image, int row, int col) {
		float x = (float)col/(float)(_width - 1) - 0.5f;
		float z = (float)row/(float)(_height - 1) - 0.5f;
		
		row = math_iclampi(row, 0, _height - 1);
		col = math_iclampi(col, 0, _width - 1);
		float y = (float)image[(row*_height+col)*3] / (float)255;
		return point(x, y, z);
	}

	void
	initvertices(unsigned char *image) {
		int total = _width * _height * POS_COMPONENT_NUM;
		float *vertices = new float[total];
		int offset = 0;
		for (int row = 0; row < _height; row++) {
			for (int col = 0; col < _width; col++) {
				point p = getpoint(image, row, col);

				vertices[offset++] = p.x;
				vertices[offset++] = p.y;
				vertices[offset++] = p.z;

				point top = getpoint(image, row - 1, col);
				point left = getpoint(image, row, col - 1);
				point right = getpoint(image, row, col + 1);
				point bottom = getpoint(image, row + 1, col);
				
				vector right2left = geometry::vectorbetween(right, left);
				vector top2bottom = geometry::vectorbetween(top, bottom);
				vector normal = right2left.crossproduct(top2bottom);
				vertices[offset++] = normal.x;
				vertices[offset++] = normal.y;
				vertices[offset++] = normal.z;
			}
		}
		_vertices = vertices;

		glGenBuffers(1, &_vbo);
		glBindBuffer(GL_ARRAY_BUFFER, _vbo);
		glBufferData(GL_ARRAY_BUFFER, total * sizeof(float), _vertices, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}

	void
	initelements() {
		short *elements = new short[_numelement];
		int offset = 0;
		for (int row = 0; row < _height - 1; row++) {
			for (int col = 0; col < _width - 1; col++) {
				short topleftindex = (short)(row * _width + col);
				short toprightindex = (short)(row * _width + col + 1);
				short bottomleftindex = (short)((row+1) * _width + col);
				short bottomrightindex = (short)((row+1) * _width + col + 1);

				elements[offset++] = topleftindex; 
				elements[offset++] = bottomleftindex; 
				elements[offset++] = toprightindex;

				elements[offset++] = toprightindex; 
				elements[offset++] = bottomleftindex; 
				elements[offset++] = bottomrightindex; 
			}
		}
		_elements = elements;

		glGenBuffers(1, &_ebo);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, _numelement * sizeof(short), _elements, GL_STATIC_DRAW);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	}

public:
	~heightmap() {
		delete[] _vertices;
		delete[] _elements;
		glDeleteBuffers(1, &_vbo);
		glDeleteBuffers(1, &_ebo);
	}

	bool
	init(const char *file) {
		int width, height;
	    unsigned char *image = SOIL_load_image(file, &width, &height, NULL, SOIL_LOAD_RGB);
		if (image == NULL)
			return false;

		_width = width;
		_height = height;

		calc_numelement();
		initvertices(image);
		initelements();

		SOIL_free_image_data(image);
		logv("heightmap param: width:%d, height:%d, num:%d", _width, _height, _numelement);
		return true;
	}

	void 
	binddata() {
		glBindBuffer(GL_ARRAY_BUFFER, _vbo);
			int stride = POS_COMPONENT_NUM * sizeof(float);
			glEnableVertexAttribArray(0);
			glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, 0);
			glEnableVertexAttribArray(1);
			glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (const void*)(3 * sizeof(float)));
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}

	void
	draw() {
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo);
			glDrawElements(GL_TRIANGLES, _numelement, GL_UNSIGNED_SHORT, NULL);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	}
};
